/*
 * Copyright (c) Kumo Inc. and affiliates.
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <iomanip>
#include <iostream>

#include <melon/gen/base.h>
#include <melon/json/dynamic.h>
#include <melon/json/json.h>
#include <melon/config.h>
#include <melon/portability/gtest.h>

using melon::Dynamic;
using melon::TypeError;

TEST(Dynamic, ArrayGenerator) {
    // Make sure arrays can be used with melon::gen.
    using namespace melon::gen;
    Dynamic arr = Dynamic::array(1, 2, 3, 4);
    EXPECT_EQ(from(arr) | take(3) | member(&Dynamic::asInt) | sum, 6);
}

TEST(Dynamic, StringPtrs) {
    Dynamic str = "12.0";
    Dynamic num = 12.0;
    Dynamic nullStr = melon::parseJson("\"foo\\u0000bar\"");

    EXPECT_EQ(0, strcmp(str.c_str(), "12.0"));
    EXPECT_EQ(0, strncmp(str.c_str(), "12.0", str.asString().length()));
    EXPECT_EQ(str.stringPiece(), "12.0");

    EXPECT_THROW(num.c_str(), TypeError);
    EXPECT_THROW(num.stringPiece(), TypeError);

    EXPECT_EQ(nullStr.stringPiece(), melon::StringPiece("foo\0bar", 7));

    nullStr.getString()[3] = '|';
    EXPECT_EQ(nullStr.stringPiece(), "foo|bar");
}

TEST(Dynamic, Getters) {
    Dynamic dStr = melon::parseJson("\"foo\\u0000bar\"");
    Dynamic dInt = 1;
    Dynamic dDouble = 0.5;
    Dynamic dBool = true;

    EXPECT_EQ(dStr.getString(), std::string("foo\0bar", 7));
    EXPECT_EQ(dInt.getInt(), 1);
    EXPECT_EQ(dDouble.getDouble(), 0.5);
    EXPECT_EQ(dBool.getBool(), true);

    dStr.getString()[3] = '|';
    EXPECT_EQ(dStr.getString(), "foo|bar");

    dInt.getInt() = 2;
    EXPECT_EQ(dInt.getInt(), 2);

    dDouble.getDouble() = 0.7;
    EXPECT_EQ(dDouble.getDouble(), 0.7);

    dBool.getBool() = false;
    EXPECT_EQ(dBool.getBool(), false);

    EXPECT_THROW(dStr.getInt(), TypeError);
    EXPECT_THROW(dStr.getDouble(), TypeError);
    EXPECT_THROW(dStr.getBool(), TypeError);

    EXPECT_THROW(dInt.getString(), TypeError);
    EXPECT_THROW(dInt.getDouble(), TypeError);
    EXPECT_THROW(dInt.getBool(), TypeError);

    EXPECT_THROW(dDouble.getString(), TypeError);
    EXPECT_THROW(dDouble.getInt(), TypeError);
    EXPECT_THROW(dDouble.getBool(), TypeError);

    EXPECT_THROW(dBool.getString(), TypeError);
    EXPECT_THROW(dBool.getInt(), TypeError);
    EXPECT_THROW(dBool.getDouble(), TypeError);
}

TEST(Dynamic, ViewTruthinessTest) {
    // An default constructed view is falsey.
    melon::const_dynamic_view view{};
    EXPECT_EQ((bool)view, false);

    Dynamic d{nullptr};

    // A view of a Dynamic that is null is still truthy.
    // Assigning a Dynamic to a view sets it to that Dynamic.
    view = d;
    EXPECT_EQ((bool)view, true);

    // reset() empties a view.
    view.reset();
    EXPECT_EQ((bool)view, false);
}

TEST(Dynamic, ViewDescendTruthinessTest) {
    melon::const_dynamic_view view{};

    Dynamic obj = Dynamic::object("key", "value");
    Dynamic arr = Dynamic::array("one", "two", "three");

    // Descending should return truthy views.
    view = obj;
    EXPECT_EQ((bool)view, true);
    view = view.descend("key");
    EXPECT_EQ((bool)view, true);

    view = arr;
    EXPECT_EQ((bool)view, true);
    view = view.descend(1);
    EXPECT_EQ((bool)view, true);
}

TEST(Dynamic, ViewDescendFailedTest) {
    melon::const_dynamic_view view{};

    Dynamic nully{};
    Dynamic obj = Dynamic::object("key", "value");
    Dynamic arr = Dynamic::array("one", "two", "three");

    // Failed descents should return empty views.
    view = nully;
    EXPECT_EQ((bool)view, true);
    view = view.descend("not an object");
    EXPECT_EQ((bool)view, false);

    view = obj;
    EXPECT_EQ((bool)view, true);
    view = view.descend("missing key");
    EXPECT_EQ((bool)view, false);

    view = arr;
    EXPECT_EQ((bool)view, true);
    view = view.descend("not an object");
    EXPECT_EQ((bool)view, false);

    view = arr;
    EXPECT_EQ((bool)view, true);
    view = view.descend(5); // out of range
    EXPECT_EQ((bool)view, false);
}

TEST(Dynamic, ViewScalarsTest) {
    Dynamic i = 5;
    Dynamic str = "hello";
    double pi = 3.14;
    Dynamic d = pi;
    Dynamic b = true;

    melon::const_dynamic_view view{};

    view = i;
    EXPECT_EQ(view.int_or(0), 5);

    view = str;
    EXPECT_EQ(view.string_or("default"), "hello");

    view = d;
    EXPECT_EQ(view.double_or(0.7), d.asDouble());

    view = b;
    EXPECT_EQ(view.bool_or(false), true);
}

TEST(Dynamic, ViewScalarsWrongTest) {
    Dynamic i = 5;
    Dynamic str = "hello";
    double pi = 3.14;
    Dynamic d = pi;
    Dynamic b = true;

    melon::const_dynamic_view view{};

    view = i;
    EXPECT_EQ(view.string_or("default"), "default");

    view = str;
    EXPECT_EQ(view.double_or(pi), pi);

    view = d;
    EXPECT_EQ(view.bool_or(false), false);

    view = b;
    EXPECT_EQ(view.int_or(777), 777);
}

TEST(Dynamic, ViewDescendObjectOnceTest) {
    double d = 3.14;
    Dynamic obj =
            Dynamic::object("string", "hello")("int", 5)("double", d)("bool", true);
    melon::const_dynamic_view view{obj};

    EXPECT_EQ(view.descend("string").string_or(""), "hello");
    EXPECT_EQ(view.descend("int").int_or(0), 5);
    EXPECT_EQ(view.descend("double").double_or(0.0), d);
    EXPECT_EQ(view.descend("bool").bool_or(false), true);
}

TEST(Dynamic, ViewDescendObjectTwiceTest) {
    double d = 3.14;
    Dynamic nested =
            Dynamic::object("string", "hello")("int", 5)("double", d)("bool", true);
    Dynamic wrapper = Dynamic::object("wrapped", nested);

    melon::const_dynamic_view view{wrapper};

    EXPECT_EQ(view.descend("wrapped").descend("string").string_or(""), "hello");
    EXPECT_EQ(view.descend("wrapped").descend("int").int_or(0), 5);
    EXPECT_EQ(view.descend("wrapped").descend("double").double_or(0.0), d);
    EXPECT_EQ(view.descend("wrapped").descend("bool").bool_or(false), true);

    EXPECT_EQ(view.descend("wrapped", "string").string_or(""), "hello");
    EXPECT_EQ(view.descend("wrapped", "int").int_or(0), 5);
    EXPECT_EQ(view.descend("wrapped", "double").double_or(0.0), d);
    EXPECT_EQ(view.descend("wrapped", "bool").bool_or(false), true);
}

TEST(Dynamic, ViewDescendObjectMissingKeyTest) {
    double d = 3.14;
    Dynamic nested =
            Dynamic::object("string", "string")("int", 5)("double", d)("bool", true);
    Dynamic wrapper = Dynamic::object("wrapped", nested);

    melon::const_dynamic_view view{wrapper};

    EXPECT_EQ(view.descend("wrapped").descend("string").string_or(""), "string");
    EXPECT_EQ(view.descend("wrapped").descend("int").int_or(0), 5);
    EXPECT_EQ(view.descend("wrapped").descend("double").double_or(0.0), d);
    EXPECT_EQ(view.descend("wrapped").descend("bool").bool_or(false), true);

    EXPECT_EQ(view.descend("wrapped", "string").string_or(""), "string");
    EXPECT_EQ(view.descend("wrapped", "int").int_or(0), 5);
    EXPECT_EQ(view.descend("wrapped", "double").double_or(0.0), d);
    EXPECT_EQ(view.descend("wrapped", "bool").bool_or(false), true);
}

TEST(Dynamic, ViewDescendObjectAndArrayTest) {
    Dynamic leaf = Dynamic::object("key", "value");
    Dynamic arr = Dynamic::array(leaf);
    Dynamic root = Dynamic::object("arr", arr);

    melon::const_dynamic_view view{root};
    EXPECT_EQ(view.descend("arr", 0, "key").string_or(""), "value");
}

std::string make_long_string() {
    return std::string(100, 'a');
}

TEST(Dynamic, ViewMoveValuesTest) {
    Dynamic leaf = Dynamic::object("key", make_long_string());
    Dynamic obj = Dynamic::object("leaf", std::move(leaf));

    melon::dynamic_view view{obj};

    const Dynamic value = view.descend("leaf").move_value_or(nullptr);
    EXPECT_TRUE(value.isObject());
    EXPECT_EQ(value.count("key"), 1);
    EXPECT_TRUE(value["key"].isString());
    EXPECT_EQ(value["key"].getString(), make_long_string());

    // Original Dynamic should have a moved-from "leaf" key.
    EXPECT_EQ(obj.count("leaf"), 1);
    EXPECT_TRUE(obj["leaf"].isObject());
    EXPECT_EQ(obj["leaf"].count("key"), 0);
}

TEST(Dynamic, ViewMoveStringsTest) {
    Dynamic obj = Dynamic::object("long_string", make_long_string());

    melon::dynamic_view view{obj};

    std::string value = view.descend("long_string").move_string_or("");
    EXPECT_EQ(value, make_long_string());

    // Original Dynamic should still have a "long_string" entry with moved-from
    // string value.
    EXPECT_EQ(obj.count("long_string"), 1);
    EXPECT_TRUE(obj["long_string"].isString());
    EXPECT_NE(obj["long_string"].getString(), make_long_string());
}

TEST(Dynamic, ViewMakerTest) {
    Dynamic d{nullptr};

    auto view = melon::make_dynamic_view(d);

    EXPECT_TRUE((std::is_same<decltype(view), melon::dynamic_view>::value));

    const Dynamic cd{nullptr};

    auto cv = melon::make_dynamic_view(cd);

    EXPECT_TRUE((std::is_same<decltype(cv), melon::const_dynamic_view>::value));

    // This should not compile, because you can't view temporaries.
    // auto fail = melon::make_dynamic_view(melon::Dynamic{nullptr});
}

TEST(Dynamic, FormattedIO) {
    std::ostringstream out;
    Dynamic doubl = 123.33;
    Dynamic dint = 12;
    out << "0x" << std::hex << ++dint << ' ' << std::setprecision(1) << doubl
            << '\n';
    EXPECT_EQ(out.str(), "0xd 1e+02\n");

    out.str("");
    Dynamic arrr = Dynamic::array(1, 2, 3);
    out << arrr;
    EXPECT_EQ(out.str(), "[1,2,3]");

    out.str("");
    Dynamic objy = Dynamic::object("a", 12);
    out << objy;
    EXPECT_EQ(out.str(), R"({"a":12})");

    out.str("");
    Dynamic objy2 = Dynamic::array(
        objy, Dynamic::object(12, "str"), Dynamic::object(true, false));
    out << objy2;
    EXPECT_EQ(out.str(), R"([{"a":12},{12:"str"},{true:false}])");
}

int main(int argc, char **argv) {
    testing::InitGoogleTest(&argc, argv);
    melon::parse_args(argc, argv, true);
    return RUN_ALL_TESTS();
}
